package com.swak.persistence.metrics;

import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.DriverPropertyInfo;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Logger;

import com.swak.Constants;
import com.swak.persistence.metrics.wrapper.ConnectionWrapper;
import com.swak.utils.ClassHelper;
import com.swak.utils.JDBCDrivers;
import com.swak.utils.Maps;

import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;

public class MetricsDriver implements Driver {

	private static MetricsDriver instance = new MetricsDriver();
	private AtomicReference<DriverHolder> main = new AtomicReference<DriverHolder>();
	private Map<String, DriverHolder> holder = Maps.newConcurrentMap();

	static {
		try {
			DriverManager.registerDriver(MetricsDriver.instance);
		} catch (SQLException e) {
			throw new IllegalStateException("Could not register MetricsDriver with DriverManager", e);
		}
	}

	@Override
	public Connection connect(String url, Properties info) throws SQLException {
		if (!acceptsURL(url)) {
			return null;
		}
		DriverHolder hodler = this.getRawDriver(url);
		return ConnectionWrapper.wrap(hodler.getDriver().connect(hodler.getUrl(), info),
				MetricsCollector.getMetricsFactory());
	}

	private DriverHolder getRawDriver(String url) throws SQLException {
		if (holder.get(url) != null) {
			return holder.get(url);
		}
		String rawUrl = this.extractRealUrl(url);
		Driver rawDriver = this.createRawDriver(rawUrl);
		this.holder.computeIfAbsent(url, (key) -> {
			return new DriverHolder().setDriver(rawDriver).setUrl(rawUrl);
		});
		main.compareAndSet(null, this.holder.get(url));
		return this.holder.get(url);
	}

	private Driver createRawDriver(String rawUrl) throws SQLException {
		String className = JDBCDrivers.getDriverClassName(rawUrl);
		Class<?> rawDriverClass = null;
		try {
			rawDriverClass = ClassHelper.forName(className);
		} catch (Exception e) {
		}
		if (rawDriverClass == null) {
			throw new SQLException("jdbc-driver's class not found. '" + className + "'");
		}
		Driver rawDriver;
		try {
			rawDriver = (Driver) rawDriverClass.getDeclaredConstructor().newInstance();
		} catch (InstantiationException e) {
			throw new SQLException("create driver instance error, driver className '" + className + "'", e);
		} catch (IllegalAccessException e) {
			throw new SQLException("create driver instance error, driver className '" + className + "'", e);
		} catch (IllegalArgumentException e) {
			throw new SQLException("create driver instance error, driver className '" + className + "'", e);
		} catch (InvocationTargetException e) {
			throw new SQLException("create driver instance error, driver className '" + className + "'", e);
		} catch (NoSuchMethodException e) {
			throw new SQLException("create driver instance error, driver className '" + className + "'", e);
		} catch (SecurityException e) {
			throw new SQLException("create driver instance error, driver className '" + className + "'", e);
		}
		return rawDriver;
	}

	@Override
	public boolean acceptsURL(String url) throws SQLException {
		return url != null && url.startsWith(Constants.Metrics_Driver_Prefix);
	}

	private String extractRealUrl(String url) throws SQLException {
		return acceptsURL(url) ? JDBCDrivers.extractRealUrl(url) : url;
	}

	@Override
	public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {
		DriverHolder hodler = this.getRawDriver(url);
		return hodler.getDriver().getPropertyInfo(hodler.getUrl(), info);
	}

	@Override
	public int getMajorVersion() {
		return this.main.get().getDriver().getMajorVersion();
	}

	@Override
	public int getMinorVersion() {
		return this.main.get().getDriver().getMinorVersion();
	}

	@Override
	public boolean jdbcCompliant() {
		return true;
	}

	@Override
	public Logger getParentLogger() throws SQLFeatureNotSupportedException {
		throw new SQLFeatureNotSupportedException("Feature not supported");
	}

	@Getter
	@Setter
	@Accessors(chain = true)
	class DriverHolder {
		private String url;
		private Driver driver;
	}
}